package org.windowkit.toolkit.widget.table;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.util.Date;

import javax.swing.BorderFactory;
import javax.swing.DefaultCellEditor;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

import org.windowkit.toolkit.widget.table.render.BlankRenderer;
import org.windowkit.toolkit.widget.table.render.DateRenderer;
import org.windowkit.toolkit.widget.table.render.FilterTableCellRenderer;

/**
 * Extension of JTable to add functionality and implement Swing bug fixes.
 *
 * @author Eric Lundin
 */
@SuppressWarnings("serial")
public class Table extends JTable {
	/**
	 * Tracks whether to allow selection
	 */
	private boolean selectionAllowed;

	/** The editor to use on the filter row */
	private final TableCellEditor filterRowEditor;

	/** Renderer to use on the filter row, and default for String.class */
	private final TableCellRenderer defaultRowRenderer;
	private final BlankRenderer blankRenderer = new BlankRenderer();

	/**
	 * Constructor for the table. Takes a table model that already exists and
	 * uses it to construct the table.
	 * @param data The table model to build the table from.
	 */
	public Table(final SortableTableModel data) {
		super(data);
		filterRowEditor = new FilterTableCellEditor();
		defaultRowRenderer = new FilterTableCellRenderer();
		setDefaultRenderer(String.class, defaultRowRenderer);
		setDefaultRenderer(Date.class, new DateRenderer("MM/dd/yyyy"));

	}

	@Override
	public SortableTableModel getModel() {
		/* Table needs to have a SortableTableModel as it's model */
		return (SortableTableModel) super.getModel();
	}

	/**
	 * Overrides JTable.isCellSelected() does not allow rows without data to be
	 * selected
	 *
	 * @param row
	 *            The row number
	 * @param col
	 *            The column number
	 */
	@Override
	public boolean isCellSelected(final int row, final int col) {
		boolean isSelected = false;
		if (getModel().getIsRowSelectable(row)) {
			isSelected = super.isCellSelected(row, col);
		}
		return isSelected;
	}

	// /**
	// * Returns the appropriate cell editor for this cell.
	// */
	@Override
	public TableCellEditor getCellEditor(final int row, final int column) {
		if (getModel().isFilterable() && row == 0) {
			// DefaultCellEditor theEditor = (DefaultCellEditor)
			// getDefaultEditor(String.class);
			// ((JTextField) theEditor.getComponent())
			// .setHorizontalAlignment(SwingConstants.TRAILING);
			// return theEditor;
			return filterRowEditor;
		}
		return super.getCellEditor(row, column);
	}

	@Override
	public TableCellRenderer getCellRenderer(final int row, final int column) {
		int offset = 0;
		if (getModel().isFilterable()) {
			offset = 1;
		}
		if (getModel().isFilterable() && row == 0) {
			return defaultRowRenderer;
		} else if (row >= getModel().getDataRowCount() + offset) {
			return blankRenderer;
		}
		return super.getCellRenderer(row, column);
	}

	/**
	 * Gets a TableColumn based on the index passed in
	 *
	 * @param columnNumber
	 *            The column to retrieve
	 * @return Returns the TableColumn representing the column index
	 */
	public TableColumn getColumn(final int columnNumber) {
		return getColumnModel().getColumn(columnNumber);
	}

	// /**
	// * Returns the runtime class of the column referenced.
	// *
	// * @param column
	// * The column to determine the class of
	// * @return The runtime class of the referenced column
	// */
	// @Override
	// public Class< ? > getColumnClass(int column) {
	// return getModel().getColumnClass(column);
	// }

	/**
	 * This public method is overridden from JTable in order to fix Bug ID:
	 * 6195469 REGRESSION: Multiple interval selection is lost in JTable if
	 * mouse is dragged
	 */
	@Override
	public void changeSelection(final int rowIndex, final int columnIndex, final boolean toggle,
			final boolean extend) {

		if (rowIndex > getModel().getDataRowCount() || !selectionAllowed) {
			return;
		}
		// else
		// super.changeSelection(rowIndex, columnIndex, toggle, extend);
		// TODO: This bug was fixed for 1.6. When we upgrade remove the code
		// below this comment and uncomment the else statement above
		ListSelectionModel rsm = getSelectionModel();
		ListSelectionModel csm = getColumnModel().getSelectionModel();

		changeSelectionModel(csm, columnIndex, false, extend);
		changeSelectionModel(rsm, rowIndex, toggle, extend);

		if (getAutoscrolls()) {
			Rectangle cellRect = getCellRect(rowIndex, columnIndex, false);
			if (cellRect != null) {
				scrollRectToVisible(cellRect);
			}
		}
	}

	/**
	 * This public method is overridden from JTable in order to fix Bug ID:
	 * 6195469 REGRESSION: Multiple interval selection is lost in JTable if
	 * mouse is dragged
	 */
	private void changeSelectionModel(final ListSelectionModel sm, final int index,
			final boolean toggle, final boolean extend) {
		// TODO: This bug was fixed for 1.6. When we upgrade remove this method.
		if (extend) {
			if (toggle) {
				sm.setAnchorSelectionIndex(index);
			} else {
				sm.setLeadSelectionIndex(index);
			}
		} else {
			if (toggle) {
				if (sm.isSelectedIndex(index)) {
					sm.removeSelectionInterval(index, index);
				} else {
					sm.addSelectionInterval(index, index);
				}
			} else {
				sm.setSelectionInterval(index, index);
			}
		}
	}

	/**
	 * Sets how users are able to select table rows from a list.
	 *
	 * @param selType
	 *            New selection type
	 */
	public void setSelectionType(final TableSelectionType selType) {
		getSelectionModel().clearSelection();
		selectionAllowed = true;
		switch (selType) {
		case NO_SELECTION:
			setRowSelectionAllowed(false);
			setColumnSelectionAllowed(false);
			selectionAllowed = false;
			break;
		case SINGLE_SELECTION:
			setRowSelectionAllowed(true);
			setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
			break;
		case SINGLE_INTERVAL_SELECTION:
			setRowSelectionAllowed(true);
			setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
			break;
		case MULTIPLE_INTERVAL_SELECTION:
			setRowSelectionAllowed(true);
			setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
			break;
		default:
			break;
		}
	}

	/**
	 * This class left aligns the text as well as putting a black line border
	 * around the field if the table has filtering enabled and we are editing
	 * the filter row.
	 *
	 * @author Eric Lundin
	 */
	@SuppressWarnings("serial")
	protected class FilterTableCellEditor extends DefaultCellEditor {
		public FilterTableCellEditor() {
			super(new JTextField());
		}

		@Override
		public Component getTableCellEditorComponent(final JTable table,
				final Object value, final boolean isSelected, final int row, final int column) {

			Component component = super.getTableCellEditorComponent(table,
					value, isSelected, row, column);

			((JTextField) component).setBorder(BorderFactory
					.createLineBorder(Color.BLACK));
			if (row == 0
					&& ((SortableTableModel) table.getModel()).isFilterable()) {

				((JTextField) component)
						.setHorizontalAlignment(SwingConstants.LEFT);
			}
			return component;
		}

	}

	/**
	 * Bug Fix for Bug 4292511. This code is needed to prevent the first column
	 * header to determine the height of the entire table header. <B>Remove this
	 * code when bug is fixed.</B>
	 *
	 * @see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4292511
	 */
	@Override
	public JTableHeader createDefaultTableHeader() {
		// TODO: Remove method when bug 4292511 is fixed.
		return new TableHeaderFix(columnModel);
	}

	public void resetHeaderHeight() {
		// TODO: Remove method and calls to method in TablePane when bug
		// 4292511 is fixed.
		((TableHeaderFix) getTableHeader()).resetHeight();
	}

	@SuppressWarnings("serial")
	protected class TableHeaderFix extends JTableHeader {
		// TODO: Remove class when bug 4292511 is fixed.
		private int preferrredHeight = -1;

		public TableHeaderFix(final TableColumnModel columnModel) {
			super(columnModel);
		}

		private Component getHeaderRenderer(final int columnIndex) {
			TableColumn aColumn = getColumnModel().getColumn(columnIndex);
			TableCellRenderer renderer = aColumn.getHeaderRenderer();
			if (renderer == null) {
				renderer = getDefaultRenderer();
			}
			return renderer.getTableCellRendererComponent(getTable(), aColumn
					.getHeaderValue(), false, false, -1, columnIndex);
		}

		private int getPreferredHeight() {

			if (preferrredHeight == -1) {
				preferrredHeight = 0;
				int columnCount = getColumnModel().getColumnCount();
				for (int column = 0; column < columnCount; column++) {
					Component comp = getHeaderRenderer(column);
					int rendererHeight = comp.getPreferredSize().height;
					preferrredHeight = Math.max(preferrredHeight,
							rendererHeight);
				}
			}
			return preferrredHeight;
		}

		@Override
		public Dimension getPreferredSize() {
			return new Dimension(super.getPreferredSize().width,
					getPreferredHeight());
		}

		@Override
		public void columnAdded(final TableColumnModelEvent e) {
			preferrredHeight = -1;
			super.columnAdded(e);
		}

		@Override
		public void columnRemoved(final TableColumnModelEvent e) {
			preferrredHeight = -1;
			super.columnRemoved(e);
		}

		public void resetHeight() {
			preferrredHeight = -1;
		}

	}



}